/**
 * Copyright 2002 Cordys R&D B.V. 
 * 
 * This file is part of the Cordys .NET Connector. 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 /**
 *
 * @author  owarnier
 * Changed by pkavuri
 */

package com.eibus.applicationconnector.ejb;

import com.eibus.applicationconnector.ejb.localization.message.Messages;
import static com.eibus.util.logger.Severity.WARN;
import static com.eibus.util.logger.Severity.ERROR;
import com.eibus.util.guid.Guid;
import com.eibus.util.logger.CordysLogger;
import com.eibus.xml.nom.Node;
import java.io.IOException;
import java.lang.ClassNotFoundException;
import java.lang.InstantiationException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.util.Hashtable;
import java.util.jar.JarFile;
import java.util.StringTokenizer;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

class EJBExecutionEnvironment {
    private Context jndiContext = null;	
    private final String MSSG_METADATA_MISSING = "Metadata is not avaialble for bean";
    private Hashtable execMetaDataCache;
    private Hashtable ejbObjectCache;
    private static Hashtable <String,String>mappedDataTypes;
    static CordysLogger ejbLogger = CordysLogger.getCordysLogger(EJBExecutionEnvironment.class);

    // Initialized the java datatypes with the corresponding xmldatatypes.
    static {
       mappedDataTypes = new Hashtable<String,String>();
	   mappedDataTypes.put("java.lang.String","string");
	   mappedDataTypes.put("void","void");
	   mappedDataTypes.put("boolean","boolean");
	   mappedDataTypes.put("byte","i1");
	   mappedDataTypes.put("short","i2");
	   mappedDataTypes.put("int","i4");
	   mappedDataTypes.put("long","i8");
	   mappedDataTypes.put("float","r4");
	   mappedDataTypes.put("double","r8");
	   mappedDataTypes.put("java.util.Date","dateTime");
	   mappedDataTypes.put("java.math.BigDecimal","number");
	   mappedDataTypes.put("java.math.BigInteger","ui8");
    }

    /** Creates a new instance of EJBExecutionEnvironment */
    EJBExecutionEnvironment() {
        if (ejbLogger.isDebugEnabled())
        	ejbLogger.debug("create EJBExecutionEnvironment");
        ejbObjectCache = new Hashtable();
    }

    /** set the deployment archives of EJBs <br>
     * This are Jar files that contain the EJB classes and a META-INF/ejb-jar.xml file
     * that describes the ejbs that are  available in this jar file.
     * @param the classpath (seperated with : or ; depending on the operating system) with the
     * jars available as EJBs
     */
    void setEJBJars(String ejbJarsClassPath) throws EJBExecutionEnvironmentException {
        if (ejbJarsClassPath == null || ejbJarsClassPath.trim().length()==0)
            throw new EJBExecutionEnvironmentException("Enterprise beans are not properly set in ClassPath",Messages.CLASS_PATH_NOT_SET);
   
        // do per jarfile:   extract the metadata and put it in the execMetaDataCache
        //  add the ejbs to the classpath
        String foundJar = null;
        String pathSeperator = System.getProperty("path.separator",";"); // windows is the default
        StringTokenizer st = new StringTokenizer(ejbJarsClassPath, pathSeperator);
        if (!st.hasMoreTokens()) {
            return;
        }
        if (execMetaDataCache != null) {
            execMetaDataCache.clear();
        }
        else {
            execMetaDataCache = new Hashtable();
        }
        EJBObjectMetaDataGenerator ejbMetaGen = new EJBObjectMetaDataGenerator();
        while (st.hasMoreTokens()) {
            foundJar = st.nextToken();
            if (foundJar.endsWith("jar")) {
                try {
                	if (ejbLogger.isDebugEnabled())
                		ejbLogger.debug("parse jarfile:" + foundJar);
                    ejbMetaGen.parseJarFile(new JarFile(foundJar));
                    execMetaDataCache.putAll(ejbMetaGen.getMetaDataObjects());
                }
                catch (IOException ioEx) {
                	if (ejbLogger.isWarningEnabled())
                		ejbLogger.log(WARN,foundJar + " gives an IOException",ioEx);
                    throw new EJBExecutionEnvironmentException(ioEx.getMessage());
                }
            }
            else {
            	if (ejbLogger.isWarningEnabled())
            		ejbLogger.log(WARN,"The given path does not contain jar files.");
                throw new EJBExecutionEnvironmentException("Enterprise beans are not properly set in ClassPath",Messages.CLASS_PATH_NOT_SET);
            }
        }
        setNamingContext();
    }


    // put this one here for further testing within the EJBConnector.
    void setNamingContext(Context context) {
        	jndiContext = context;
    }

    private void setNamingContext() throws EJBExecutionEnvironmentException {
		try {
			System.setSecurityManager(new java.rmi.RMISecurityManager());
			jndiContext = (Context) new InitialContext();
		}
		catch (NamingException nEx) {
			if (ejbLogger.isEnabled(ERROR))
				ejbLogger.log(ERROR,"getNamingContext: could not create the Context",nEx);
			throw new EJBExecutionEnvironmentException("getNamingContext: could not create the Context due to:" + nEx.getMessage(),Messages.CONTEXT_CREATE_FAILED);
		}
    }

    // Return the namingContext.
    Context getNamingContext() throws EJBExecutionEnvironmentException {
        if (jndiContext == null) {
            if (ejbLogger.isDebugEnabled())
            	ejbLogger.debug("getNamingContext: return a newly created Context");
            setNamingContext();
        }
        else {
        	  if (ejbLogger.isDebugEnabled())
            	ejbLogger.debug("getNamingContext: return existing Context");
        }
        return jndiContext;
    }

    /** get the MetaData on a specific EJB that is within this execution environment.
     * @see EJBObjectMetaData
     * @param the name of the EJB as known via JNDI. this should match the ejb name within the ejb-jar.xml file
     */
    EJBObjectMetaData getMetaData(String ejbName) throws EJBExecutionEnvironmentException {
        // check the hashtable and return the EJBObjectMetaData
        if (ejbName == null) 
		{
			throw new EJBExecutionEnvironmentException("Invalid bean name",Messages.INVALID_BEAN_NAME);
        }
        EJBObjectMetaData tmpMD = (EJBObjectMetaData) execMetaDataCache.get(ejbName);
        if (tmpMD == null){
        	if (ejbLogger.isEnabled(ERROR))
        		ejbLogger.log(ERROR,"Metadata for " + ejbName + " not found.");
              throw new EJBExecutionEnvironmentException("metadata for " + ejbName + " not found",Messages.METADATA_NOT_AVAILABLE,new String[]{ejbName});
        }
        return tmpMD;
    }

    /** start a EJBCall <br>
     * This constructs the operating environment (the execution environment and the ejb jars) and returnes
     * this environment in a seperate memory space that has everything in the execute one of the EJBs
     * available.
     * @see EJBCall
     * @parameter the JNDI name of the EJB that you want to call
     * @return EJBCall
     */
    EJBCall startCall(String ejbName) throws EJBExecutionEnvironmentException {
    	if (ejbName == null || ejbName.length() == 0)
			throw new EJBExecutionEnvironmentException("Invalid bean name",Messages.INVALID_BEAN_NAME);
        
        // connect the classpaths together and put them into the classpath of the classloader
        EJBCall tbReturned = null;
        try {
        	if (ejbLogger.isDebugEnabled())
        		ejbLogger.debug("Loading the EJBCall class " + ejbName);
            // Class ejbCallClass = ejbClassLoader.loadClass("com.cordys.applicationconnector.ejb.EJBCall");
            Class ejbCallClass = Class.forName("com.eibus.applicationconnector.ejb.EJBCall");
            Constructor constructor = ejbCallClass.getDeclaredConstructor(new Class[] {String.class} );
            tbReturned = (EJBCall) constructor.newInstance(new Object[] {ejbName} );
            tbReturned.setExecutionEnvironment(this);
        }
        catch (ClassNotFoundException cnfEx) {
        	if (ejbLogger.isErrorEnabled())
        		ejbLogger.error("EJBCall class not found.",cnfEx);
               throw new EJBExecutionEnvironmentException(cnfEx.getMessage(),Messages.EJB_CALL_CLASS_NOT_FOUND);
        }
        catch (InstantiationException iEx) {
        	if (ejbLogger.isErrorEnabled())
        		ejbLogger.error("Unable to instantiate EJBCall class.",iEx);
        	throw new EJBExecutionEnvironmentException(iEx.getMessage(),Messages.COULD_NOT_INSTANTIATE_CLASS);
        }
        catch (IllegalAccessException iaEx) {
        	if (ejbLogger.isErrorEnabled())
        		ejbLogger.error("Unable to access the EJBCall class.",iaEx);
        		throw new EJBExecutionEnvironmentException(iaEx.getMessage(),Messages.COULD_NOT_ACCESS_CLASS);
        }
        catch (InvocationTargetException itEx) {
        	if (ejbLogger.isErrorEnabled())
                ejbLogger.error("Unable to invoke the constructor of the EJBCall class." , itEx);
			throw new EJBExecutionEnvironmentException(itEx.getMessage(),Messages.COULD_NOT_INVOKE_CLASS);
        }
        catch (NoSuchMethodException nsmEx) {
        	if (ejbLogger.isErrorEnabled())
                ejbLogger.error("No constructor found for EJBCall class." , nsmEx);
			throw new EJBExecutionEnvironmentException(nsmEx.getMessage(),Messages.CONSTRUCTOR_NOT_FOUND);
        }
		catch (Exception ex) {
		  	if (ejbLogger.isErrorEnabled())
                ejbLogger.error("Unknown Exception." , ex);
	
			throw new EJBExecutionEnvironmentException("Unknown Exception." + ex.getMessage());
        }
		return tbReturned;
    } // end of startCall


    /**
     * add the EJBCall object to Collection;
     */
    void addEJBCall(Guid key, EJBCall ejbCall) throws EJBExecutionEnvironmentException {
    	if (key == null || ejbCall == null )
    		throw new EJBExecutionEnvironmentException("Improper key-value pairs",Messages.IMPROPER_KEY_VALUE_PAIRS);
        if (ejbObjectCache.containsKey(key)) 	// Never takes place
            return;
        ejbObjectCache.put(key, ejbCall);
    }

    /**
     *  Picks up the object from the collection
     */
    EJBCall getEJBCall(Guid key) throws EJBExecutionEnvironmentException {
		if (key == null )
			throw new EJBExecutionEnvironmentException("Improper key value",Messages.IMPROPER_KEY_VALUE);
        if (ejbObjectCache.containsKey(key)) 	// Never takes place
            return (EJBCall) ejbObjectCache.get(key);
        throw new EJBExecutionEnvironmentException("Object with key " + key.toString()+ " does not exist",Messages.OBJECT_DOES_NOT_EXIST,new String[]{key.toString()});
    }

    // this methods get the list of beans deployed in app server.
    void getBeans(String type, int response) throws EJBExecutionEnvironmentException 
	{
		java.util.Enumeration enums = execMetaDataCache.keys();
        com.eibus.xml.nom.Document document = Node.getDocument(response);
        int tupleNode = document.createElementWithParentNS("tuple",null, response);
        int oldNode = document.createElementWithParentNS("old",null, tupleNode);
        while (enums.hasMoreElements()) 
		{
			String beanName =  (String) enums.nextElement();
			Object obj=execMetaDataCache.get(beanName);
			if(obj instanceof java.lang.String)
			{
				int node = document.createElementWithParentNS("bean", beanName, oldNode);
				Node.setAttribute(node,"type","None");
			}
			else if (obj instanceof EJBObjectMetaData)
			{
				EJBObjectMetaData tmpMetaData = (EJBObjectMetaData)obj;
				String beanType = tmpMetaData.getEJBType();
				if (type.equalsIgnoreCase(beanType)) 
				{
					int node = document.createElementWithParentNS("bean", beanName, oldNode);
					Node.setAttribute(node,"type",tmpMetaData.getSessionType());
				}
			}//else
		}//while
    }//method

    // this method gets the info of specified bean.
    void getBeanInfo(String beanName, int response) throws EJBExecutionEnvironmentException {
        if (null == beanName || "".equals(beanName))
            throw new EJBExecutionEnvironmentException("Bean name is missing. Must Specify the Bean Name.",Messages.BEAN_NAME_MISSING);
        
        if (ejbLogger.isDebugEnabled())
			ejbLogger.debug("R - calleing getMetaData");	
        EJBObjectMetaData tmpMetaData = getMetaData(beanName);
        if (ejbLogger.isDebugEnabled())
			ejbLogger.debug("R - returned from  getMetaData");	
		if (tmpMetaData == null) 
		{
			if (ejbLogger.isWarningEnabled())
				ejbLogger.log(WARN,MSSG_METADATA_MISSING + beanName);
			throw new EJBExecutionEnvironmentException(MSSG_METADATA_MISSING + beanName,Messages.METADATA_NOT_AVAILABLE,new String[]{beanName});
		}
        com.eibus.xml.nom.Document document = Node.getDocument(response);
        int tupleNode = document.createElementWithParentNS("tuple",null, response);
        int oldNode = document.createElementWithParentNS("old", null,tupleNode);
        int beanNode = document.createElementWithParentNS(beanName, null,oldNode);

        document.createTextElement("home",tmpMetaData.getRemoteHomeName(),  beanNode);
        document.createTextElement("remote", tmpMetaData.getRemoteName(), beanNode);
        document.createTextElement("ejbClass", tmpMetaData.getEjbClass(), beanNode);
        String beanType = tmpMetaData.getEJBType();
        document.createTextElement("ejbtype", beanType , beanNode);
        document.createTextElement("session-type",  tmpMetaData.getSessionType(), beanNode);
		document.createTextElement("transaction-type",  tmpMetaData.getTransactionType(), beanNode);
		
		if (ejbLogger.isDebugEnabled())
			ejbLogger.debug("metadata response "+ Node.writeToString(response, true));	
    }

    // this methods gets the info of the sepcified interface.
    void getInterfaceInfo(String beanName, String className, int response)
    				throws EJBExecutionEnvironmentException {
        if (null == className || "".equals(className) )
            throw new EJBExecutionEnvironmentException("Interface name is missing. Improper request.",Messages.INTERFACE_NAME_MISSING);
     //   System.out.println(beanName);
		EJBObjectMetaData tmpMetaData = getMetaData(beanName);
		if (tmpMetaData == null) {
			if (ejbLogger.isWarningEnabled())
				ejbLogger.warn(MSSG_METADATA_MISSING + beanName);
				throw new EJBExecutionEnvironmentException(MSSG_METADATA_MISSING + beanName,Messages.METADATA_NOT_AVAILABLE,new String[]{beanName});
		}
        try {
   //   System.out.println(tmpMetaData.getRemoteName());
      if (!(className.equals(tmpMetaData.getRemoteName()) || className.equals(tmpMetaData.getRemoteHomeName())))
	  	throw new EJBExecutionEnvironmentException("Invalid Interface " + className +" for " + beanName,Messages.INVALID_INTERFACE,new String[]{className,beanName});
        	Class interfaceObject = Class.forName(className);
     	    boolean isHomeInterface = false;
			if (Class.forName("javax.ejb.EJBHome").isAssignableFrom(interfaceObject)) {
					isHomeInterface = true;
			}
            Method[] methodList = interfaceObject.getDeclaredMethods();

            com.eibus.xml.nom.Document document = Node.getDocument(response);
            int tupleNode = document.createElementWithParentNS("tuple",null, response);
            int oldNode = document.createElementWithParentNS("old",null, tupleNode);
            int classNode = document.createElementWithParentNS(className, null,oldNode);
            int methodsNode = document.createElementWithParentNS("methods", null,classNode);
            for(int i=0;i<methodList.length;i++) {
            	boolean isHomeReturnRemote = false;
            	if (isHomeInterface) {
//					System.out.println(methodList[i].getReturnType().getName());
//					System.out.println(tmpMetaData.getRemoteName());
            		if ((methodList[i].getReturnType().getName()).equals(tmpMetaData.getRemoteName()))
						isHomeReturnRemote = true;
					else
						continue;
            	}
				if (!isHomeReturnRemote)
				     if (!checkForPrimitive(methodList[i].getReturnType()))
				     	continue;
//				System.out.println(methodList[i].getName());
                Class[] methodParameters = methodList[i].getParameterTypes();
				int methodNode = document.createElementWithParentNS("method", null,methodsNode);
				document.createTextElement("signature",methodList[i].toString(), methodNode);
				document.createTextElement("name",methodList[i].getName(), methodNode);
//	Explicitly specifying the return type as string.class since it is home interface's create method
				if (isHomeReturnRemote)
					document.createTextElement("returntype",getxmlDatatype(java.lang.String.class), methodNode);
				else
					document.createTextElement("returntype",getxmlDatatype(methodList[i].getReturnType()), methodNode);
                int parametersElement = document.createElementWithParentNS("parameters", null,methodNode);
                boolean objectFlag = false;
                for(int j = 0 ;j < methodParameters.length ; j++) {
                    Class parameter = methodParameters[j];
                    boolean datatypeOfParam = checkForPrimitive(parameter);
                    if(!datatypeOfParam) {
                    	objectFlag = true;
						break;
                    }
                	int eachParameterElement = document.createElement("parameter");
                    // 1. create name element.
                    int eachParamNameElement = document.createElement("name");
                    if(parameter.getName().equals("java.lang.String")) {
                        int eachParamNameValue = document.createText("stringParam");
                        Node.appendToChildren(eachParamNameValue, eachParamNameElement);
                        Node.appendToChildren(eachParamNameElement,eachParameterElement);
                    }
                    else if(parameter.getName().equals("java.util.Date")) {
                        int eachParamNameValue = document.createText("dateTimeParam");
                        Node.appendToChildren(eachParamNameValue, eachParamNameElement);
                        Node.appendToChildren(eachParamNameElement,eachParameterElement);
                    }
                    else {
                        int eachParamNameValue = document.createText(parameter.getName()+"Param");
                        Node.appendToChildren(eachParamNameValue, eachParamNameElement);
                        Node.appendToChildren(eachParamNameElement,eachParameterElement);
                    }
                    int eachParamDatatypeElement = document.createElement("datatype");
                    String parameterDatatype = getxmlDatatype(parameter);
                    int eachParamDatatypeElementValue = document.createText(parameterDatatype);
                    Node.appendToChildren( eachParamDatatypeElementValue,eachParamDatatypeElement);
                    Node.appendToChildren(eachParamDatatypeElement,eachParameterElement);
                    Node.appendToChildren( eachParameterElement,parametersElement);
                }// end of paramters looop.
                if (objectFlag) {
                	// Node.unlink(methodNode);
                	if(methodNode > 0)
                	{
                		Node.delete(methodNode);
                		methodNode = 0;
                	}
                }
             }
          }
          catch(ClassNotFoundException e) {
            throw new EJBExecutionEnvironmentException(e.getMessage());
          }
          catch(SecurityException e) {
            throw new EJBExecutionEnvironmentException(e.getMessage());
          }
    }
    /**
     *checks whether the given method is primitive or not.
     */
    private static boolean checkForPrimitive(Class dataTypeClazz) {
        return mappedDataTypes.containsKey((Object) dataTypeClazz.getName());
    }

    /**
     *  This methods is used to retrive the corresponding xml data type for
     *  java data type. here we are support only primitive data types.
     */
    private String getxmlDatatype(Class dataTypeClazz) {
        String  dataType = dataTypeClazz.getName();
        if (mappedDataTypes.containsKey((Object) dataType))
            return (String) mappedDataTypes.get((Object) dataType);
        return "";
    }
}
